import sys
import threading
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                              QHBoxLayout, QLabel, QLineEdit, QPushButton, 
                              QListWidget, QListWidgetItem, QFileDialog, QMessageBox, QInputDialog, QTextEdit, QDialog, QCheckBox, QMenu, QProgressDialog)
from PySide6.QtGui import QContextMenuEvent, QIcon
from PySide6.QtCore import Qt, QSettings, Signal, QThread
from PySide6.QtGui import QTextOption, QTextCursor
from PySide6.QtWidgets import QStyle


import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.header import Header

import json
import os
import copy
import subprocess
import shutil
# from apkutils import APK
from pyaxmlparser import APK
import datetime
from urllib.parse import urlparse
from screen import Screenshot
import base64

COMMIT_FORMAT = "[%1][%2][%3][%4][%5]"

mode = 1  # 0: 测试模式，1: 发布模式

def load_email_settings():
    settings = QSettings("GitLabPublisher", "Projects")
    smtp_server = str(settings.value("smtp_server", ""))
    smtp_port = str(settings.value("smtp_port", ""))
    smtp_user = str(settings.value("smtp_user", ""))
    smtp_password = str(settings.value("smtp_password", ""))
    return [smtp_server, smtp_port, smtp_user, smtp_password]

def copy_file_with_optional_name(self, src_file, dest_path, dest_name=None):
    """
    复制文件到目标路径，可选择是否重命名，并根据项目配置中的APP_PATH和OVERLAY_PATH进行额外复制
    :param src_file: 源文件路径
    :param dest_path: 目标目录路径
    :param dest_name: 可选的目标文件名，如果为None则保持原文件名
    :return: True如果复制成功，False如果复制失败
    """
    try:
        print(f"开始复制文件: {src_file} 到 {dest_path}")
        print(f"目标文件名: {dest_name if dest_name else '保持原文件名'}")
        
        if not os.path.exists(dest_path):
            print(f"创建目标路径: {dest_path}")
            os.makedirs(dest_path)
            
        if dest_name:
            # 确保目标文件名保持与源文件相同的后缀
            src_ext = os.path.splitext(src_file)[1]
            dest_name = dest_name + src_ext
            dest_file = os.path.join(dest_path, dest_name)
        else:
            dest_file = os.path.join(dest_path, os.path.basename(src_file))
            
        dest_file_name = os.path.basename(dest_file)
        print(f"初始目标文件路径: {dest_file}")

        # 检查文件名是否包含APP_PATH或OVERLAY_PATH配置值，并复制到相应目录
        if self.config:
            app_path = self.config.get("APP_PATH", "")
            overlay_path = self.config.get("OVERLAY_PATH", "")
            file_name = os.path.basename(src_file)
            app_dest_path = dest_path
            print(f"项目配置: APP_PATH={app_path}, OVERLAY_PATH={overlay_path}")
            
            # 只有APK文件才执行特殊路径处理
            if file_name.lower().endswith('.apk'):
               # 处理主题apk文件
                if overlay_path and 'Overlay' in file_name:

                    #校验是否生成文件夹
                    if self.config.get("CREATE_FOLDER", False):
                        # 提取文件名（不包含扩展名）
                        file_name_without_ext = os.path.splitext(dest_file_name)[0]
                        app_dest_path = os.path.join(dest_path, overlay_path, file_name_without_ext)
                    else:
                        app_dest_path = os.path.join(dest_path, overlay_path)

                else:
                    #校验是否生成文件夹
                    if self.config.get("CREATE_FOLDER", False):
                        # 提取文件名（不包含扩展名）
                        file_name_without_ext = os.path.splitext(dest_file_name)[0]
                        app_dest_path = os.path.join(dest_path, app_path, file_name_without_ext)
                    else:
                        app_dest_path = os.path.join(dest_path, app_path)
                        

            print(f"根据文件名判断目标路径: {app_dest_path}")
            if not os.path.exists(app_dest_path):
                print(f"创建APP_PATH路径: {app_dest_path}")
                os.makedirs(app_dest_path)
            
           
            dest_file = os.path.join(app_dest_path, dest_file_name)   
            print(f"最终目标文件路径: {dest_file}")

        # 复制文件到目标路径
        self.log_signal.emit(f"正在复制文件: {src_file} 到 {dest_file} \n", False)
        shutil.copy2(src_file, dest_file)
        self.log_signal.emit(f"文件复制成功 \n", False)
        return True
    except Exception as e:
        print(f"文件复制失败: {str(e)}")
        return False

class ProjectManager(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("GitLab Publisher")
        
        # 获取屏幕尺寸并计算居中位置
        screen = QApplication.primaryScreen().availableGeometry()
        width, height = 1200, 800
        x = (screen.width() - width) // 2
        y = (screen.height() - height) // 2
        self.setGeometry(x, y, width, height)
        
        self.projects = {}
        self.current_project = None
        self.copy_rules = {}
        self.init_ui()
        self.load_projects()

    def init_ui(self):
        # Main widget and layout
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        layout = QHBoxLayout(main_widget)

        # Left panel - Project list
        left_panel = QWidget()
        left_layout = QVBoxLayout(left_panel)
        
        self.project_list = QListWidget()
        self.project_list.itemClicked.connect(self.select_project)
        
        # 添加右键菜单
        self.project_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.project_list.customContextMenuRequested.connect(self.show_context_menu)
        # 创建水平布局放置Projects标签和邮件配置按钮
        projects_header = QWidget()
        projects_header_layout = QHBoxLayout(projects_header)
        projects_header_layout.setContentsMargins(0, 0, 0, 0)
        projects_header_layout.setSpacing(0)
        projects_header_layout.addWidget(QLabel("Projects:"))
        projects_header_layout.addStretch()
        
        # 添加邮件配置按钮
        self.btn_email_config = QPushButton()
        
        self.btn_email_config.setIcon(self.style().standardIcon(getattr(QStyle, 'SP_FileDialogDetailedView')))
        self.btn_email_config.clicked.connect(self.show_options_menu)
        projects_header_layout.addWidget(self.btn_email_config)
        
        left_layout.addWidget(projects_header)
        left_layout.addWidget(self.project_list)
        
        btn_new = QPushButton("New Project")
        btn_new.clicked.connect(self.new_project)
        left_layout.addWidget(btn_new)
        
       
        btn_publish = QPushButton("Publish")
        btn_publish.clicked.connect(self.open_publisher)
        left_layout.addWidget(btn_publish)
        
       
        layout.addWidget(left_panel, stretch=1)
        
        # Right panel - Project details
        self.right_panel = QWidget()
        self.right_layout = QVBoxLayout(self.right_panel)
        
        self.project_name = QLineEdit(placeholderText="M1EFL1")
        self.repo_url = QLineEdit(placeholderText="git@3rd-devops.desaysv.com:chery/g7/lion/m1efl1.git")
        self.target_branches = QLineEdit(placeholderText="main,develop")
        self.local_git_path = QLineEdit(placeholderText="D:\\233957\\m1efl1\\gitlab\\m1efl1")
        self.target_path = QLineEdit(placeholderText="")
        self.app_path = QLineEdit(placeholderText="prebuilts")
        self.overlay_path = QLineEdit(placeholderText="overlays")
        self.exact_match_checkbox = QCheckBox("绝对匹配（仅复制匹配规则的APK）国际项目请选择此项，国内项目一般取消选择")
        self.exact_match_checkbox.setChecked(True)
        self.create_folder_checkbox = QCheckBox("根据文件名生成文件夹 德赛平台一般需要选择此项，美佳平台一般取消选择")
        self.create_folder_checkbox.setChecked(False)
        self.screenshot_checkbox = QCheckBox("是否截屏")
        self.screenshot_url = QLineEdit(placeholderText="请输入当前项目的远程根路径URL，比如https://mega-gitlab.megatronix.com:8899/chery/lion_8155_internation")
        self.server_account = QLineEdit(placeholderText="服务器账号")
        self.server_pwd = QLineEdit(placeholderText="服务器密码")
        self.btn_manage_rules = QPushButton("Manage Rules")
        self.btn_manage_rules.clicked.connect(self.open_rule_dialog)
        
        self.right_layout.addWidget(QLabel("Project Name:"))
        self.right_layout.addWidget(self.project_name)

        self.right_layout.addWidget(QLabel("Git Remote URL:"))
        self.right_layout.addWidget(self.repo_url)

        self.right_layout.addWidget(QLabel("Target Branches:"))

        # 创建水平布局放置分支标签和选择按钮
        branches_header = QWidget()
        branches_header_layout = QHBoxLayout(branches_header)
        branches_header_layout.setContentsMargins(0, 0, 0, 0)
        branches_header_layout.setSpacing(0)
        branches_header_layout.addWidget(self.target_branches)
          
        # 添加分支选择按钮
        self.btn_select_branches = QPushButton("选择分支")
        branches_header_layout.addWidget(self.btn_select_branches)
        self.btn_select_branches.clicked.connect(self.select_branches)
        
        
        self.right_layout.addWidget(branches_header)

        self.right_layout.addWidget(QLabel("Local Git Path(本地项目的git仓库地址):"))
        self.right_layout.addWidget(self.local_git_path)

        self.right_layout.addWidget(QLabel("Target Path(相对于Local Git Path的地址，德赛的项目基本就是在git仓库目录下面，美佳项目一般不一样):"))
        self.right_layout.addWidget(self.target_path)

        self.right_layout.addWidget(QLabel("App Path(一般多主题项目有一个单独的app目录如T22、T26)，否则不要填:"))
        self.right_layout.addWidget(self.app_path)

        self.right_layout.addWidget(QLabel("Overlay Path(一般多主题项目有一个单独的overlay目录如T22、T26)，否则不要填:"))
        self.right_layout.addWidget(self.overlay_path)

        self.right_layout.addWidget(self.exact_match_checkbox)
        self.right_layout.addWidget(self.create_folder_checkbox)
        # 创建一个水平布局来包含截屏复选框和输入框
        screenshot_container = QWidget()
        screenshot_layout = QHBoxLayout(screenshot_container)
        screenshot_layout.setContentsMargins(0, 0, 0, 0)
        screenshot_layout.setSpacing(0)
        screenshot_layout.addWidget(self.screenshot_checkbox)
        screenshot_layout.addWidget(self.screenshot_url)
        self.right_layout.addWidget(screenshot_container)
        # 创建一个水平布局来包含服务器账号和密码输入框
        server_auth_container = QWidget()
        server_auth_layout = QHBoxLayout(server_auth_container)
        server_auth_layout.setContentsMargins(0, 0, 0, 0)
        server_auth_layout.setSpacing(0)
        server_auth_layout.addWidget(QLabel("服务器账号:"))
        server_auth_layout.addWidget(self.server_account)
        server_auth_layout.addWidget(QLabel("服务器密码:"))
        server_auth_layout.addWidget(self.server_pwd)
        self.right_layout.addWidget(server_auth_container)
        self.right_layout.addWidget(self.btn_manage_rules)
        self.right_layout.addWidget(QLabel("Rules:"))
        self.rule_list = QListWidget()
        self.right_layout.addWidget(self.rule_list)
        
        btn_save = QPushButton("Save")
        btn_save.clicked.connect(self.save_project)
        self.right_layout.addWidget(btn_save)
        
        layout.addWidget(self.right_panel, stretch=2)
        
        # Initially hide right panel
        self.right_panel.hide()

    def show_options_menu(self):
        menu = QMenu()
        import_action = menu.addAction("导入项目")
        import_action.triggered.connect(self.import_projects)
        export_action = menu.addAction("导出项目")
        export_action.triggered.connect(self.export_projects)
        email_action = menu.addAction("邮件配置")
        email_action.triggered.connect(self.open_email_settings)
        menu.exec(self.btn_email_config.mapToGlobal(self.btn_email_config.rect().bottomLeft()))
        
    def select_branches(self):
        """
        获取远程分支列表并显示选择对话框
        """
        branch_progress_dialog = QProgressDialog("正在获取分支列表...", "", 0, 0, self)
        branch_progress_dialog.setWindowTitle("获取分支列表")
        branch_progress_dialog.setWindowModality(Qt.WindowModality.WindowModal)
        branch_progress_dialog.setWindowFlags(branch_progress_dialog.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
        branch_progress_dialog.setModal(True)
        branch_progress_dialog.setCancelButton(None)
        branch_progress_dialog.reset()
        
        QApplication.processEvents()
        
        if not self.repo_url.text():
            branch_progress_dialog.close()
            QMessageBox.warning(self, "Warning", "请先填写Git仓库URL")
            return
            
        try:
            branch_progress_dialog.show()
            QApplication.processEvents()
            # 执行git命令获取远程分支列表
            result = subprocess.run(["git", "ls-remote", "--heads", self.repo_url.text()], 
                                  capture_output=True, text=True)
            
            if result.returncode != 0:
                branch_progress_dialog.close()
                QMessageBox.warning(self, "Error", f"获取分支失败: {result.stderr}")
                return
            branch_progress_dialog.close()
            # 解析分支名称
            branches = [line.split("refs/heads/")[-1].strip() 
                      for line in result.stdout.splitlines() 
                      if "refs/heads/" in line]
                      
            if not branches:
                QMessageBox.warning(self, "Warning", "未找到任何分支")
                return
                
            # 创建分支选择对话框
            dialog = QDialog(self)
            dialog.setWindowTitle("选择分支")
            dialog.setMinimumWidth(300)
            layout = QVBoxLayout(dialog)
            
            # 添加分支列表(多选)
            list_widget = QListWidget()
            list_widget.setSelectionMode(QListWidget.SelectionMode.MultiSelection)
            for branch in branches:
                list_widget.addItem(branch)
                
            # 添加确定按钮
            btn_ok = QPushButton("确定")
            btn_ok.clicked.connect(dialog.accept)
            
            layout.addWidget(list_widget)
            layout.addWidget(btn_ok)
            
            if dialog.exec() == QDialog.DialogCode.Accepted:
                selected_items = list_widget.selectedItems()
                if selected_items:
                    selected_branches = ",".join([item.text() for item in selected_items])
                    self.target_branches.setText(selected_branches)
                    
        except Exception as e:
            if branch_progress_dialog and branch_progress_dialog.isVisible():
                branch_progress_dialog.close()

            QMessageBox.critical(self, "Error", f"获取分支时出错: {str(e)}")

    def load_projects(self):
        settings = QSettings("GitLabPublisher", "Projects")
        projects_data = settings.value("projects", "{}")
        self.projects = json.loads(str(projects_data))
        self.update_project_list()

        self.smtp_server, self.smtp_port, self.smtp_user, self.smtp_password = load_email_settings()

        if not all([self.smtp_server, self.smtp_port, self.smtp_user]):
            print("SMTP配置不完整，请检查邮件服务器设置")
        else:
            print(f"SMTP Server: {self.smtp_server}, Port: {self.smtp_port}, User: {self.smtp_user}")

    def save_projects(self):
        settings = QSettings("GitLabPublisher", "Projects")
        settings.setValue("projects", json.dumps(self.projects))

    def update_project_list(self):
        self.project_list.clear()
        for project_name in self.projects:
            self.project_list.insertItem(0, project_name)

    def select_project(self, item):
        project_name = item.text()
        self.current_project = project_name
        project_data = self.projects[project_name]
        
        self.project_name.setText(project_name)
        self.repo_url.setText(project_data.get("REPO_URL", ""))
        self.target_branches.setText(project_data.get("TARGET_BRANCHES", ""))
        self.local_git_path.setText(project_data.get("LOCAL_GIT_PATH", ""))
        self.target_path.setText(project_data.get("TARGET_PATH", ""))
        self.app_path.setText(project_data.get("APP_PATH", ""))
        self.overlay_path.setText(project_data.get("OVERLAY_PATH", ""))
        self.copy_rules = project_data.get("COPY_RULES", {})
        self.exact_match_checkbox.setChecked(project_data.get("EXACT_MATCH", True))
        self.create_folder_checkbox.setChecked(project_data.get("CREATE_FOLDER", False))
        self.screenshot_checkbox.setChecked(project_data.get("SCREENSHOT_ENABLED", False))
        self.screenshot_url.setText(project_data.get("SCREENSHOT_URL", ""))
        self.server_account.setText(project_data.get("SERVER_ACCOUNT", ""))
        self.server_pwd.setText(project_data.get("SERVER_PWD", ""))
        self.update_rule_list()
        self.right_panel.show()

    def new_project(self):
        name, ok = QInputDialog.getText(self, "New Project", "Enter project name:")
        if ok and name:
            if name in self.projects:
                QMessageBox.warning(self, "Warning", "Project already exists!")
                return
            
            self.projects[name] = {
                "PROJECT_NAME": name,
                "REPO_URL": "",
                "TARGET_BRANCHES": "",
                "LOCAL_GIT_PATH": "",
                "TARGET_PATH": "",
                "APP_PATH": "",
                "OVERLAY_PATH": "",
                "COPY_RULES": {},
                "EXACT_MATCH": True,
                "CREATE_FOLDER": False,
                "SCREENSHOT_ENABLED": False,
                "SCREENSHOT_URL": "",
                "SERVER_ACCOUNT": "",
                "SERVER_PWD": ""
            }
            self.save_projects()
            self.update_project_list()
            self.project_list.setCurrentRow(self.project_list.count() - 1)

    def delete_project(self, project):
        
        if not project:
            return
            
        reply = QMessageBox.question(self, "Delete Project", 
                                    f"Are you sure you want to delete {project}?",
                                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
        
        if reply == QMessageBox.StandardButton.Yes:
            del self.projects[project]
            self.save_projects()
            self.update_project_list()
            if project == self.current_project:
                self.current_project = None
                self.right_panel.hide()
                
    def copy_project(self, project_name):
        if project_name not in self.projects:
            return
            
        new_name = f"{project_name} Copy"
        while new_name in self.projects:
            new_name = f"{new_name} Copy"
            
        # 复制项目配置
        new_project = self.projects[project_name].copy()
        # 更新项目名
        new_project["PROJECT_NAME"] = new_name
        self.projects[new_name] = new_project
        
        # 保存并刷新列表
        self.save_projects()
        self.update_project_list()
            
    def show_context_menu(self, pos):
        # 获取右键点击位置的项目
        item = self.project_list.itemAt(pos)
        if not item:
            return
                    
        menu = QMenu()
        delete_action = menu.addAction("删除项目")
        delete_action.triggered.connect(lambda: self.delete_project(item.text()))
        copy_action = menu.addAction("复制项目")
        copy_action.triggered.connect(lambda: self.copy_project(item.text()))
        menu.exec(self.project_list.mapToGlobal(pos))


    def save_project(self):
        if not self.current_project:
            return
            
        project_name = self.project_name.text()
        if project_name != self.current_project:
            # 如果项目名称被修改，先删除旧项目再创建新项目
            project_data = self.projects.pop(self.current_project)
            self.projects[project_name] = project_data
            self.current_project = project_name
            
        if self.screenshot_checkbox.isChecked() and not self.screenshot_url.text():
            QMessageBox.warning(self, "Warning", "请输入项目的远程根目录URL用于截屏")
            return

        if self.screenshot_checkbox.isChecked() and not self.server_account.text() and not self.server_pwd.text():
            QMessageBox.warning(self, "Warning", "请输入用于登录gitlab服务器的账号和密码")
            return
            
        self.projects[self.current_project] = {
            "PROJECT_NAME": self.project_name.text(),
            "REPO_URL": self.repo_url.text(),
            "TARGET_BRANCHES": self.target_branches.text(),
            "LOCAL_GIT_PATH": self.local_git_path.text(),
            "TARGET_PATH": self.target_path.text(),
            "APP_PATH": self.app_path.text(),
            "OVERLAY_PATH": self.overlay_path.text(),
            "COPY_RULES": self.copy_rules,
            "EXACT_MATCH": self.exact_match_checkbox.isChecked(),
            "CREATE_FOLDER": self.create_folder_checkbox.isChecked(),
            "SCREENSHOT_ENABLED": self.screenshot_checkbox.isChecked(),
            "SCREENSHOT_URL": self.screenshot_url.text(),
            "SERVER_ACCOUNT": self.server_account.text(),
            "SERVER_PWD": self.server_pwd.text()
        }
        self.save_projects()
        self.update_project_list()
        
        # 保持当前项目的选中状态
        items = self.project_list.findItems(self.current_project, Qt.MatchFlag.MatchExactly)
        if items:
            self.project_list.setCurrentItem(items[0])
            
        QMessageBox.information(self, "Success", "Project saved successfully!")

    def open_publisher(self):
        if not self.current_project:
            QMessageBox.warning(self, "Warning", "Please select a project first!")
            return
            
        # 检查关键配置是否已填写
        project_data = self.projects[self.current_project]
        required_fields = ["REPO_URL", "TARGET_BRANCHES", "LOCAL_GIT_PATH"]
        missing_fields = [field for field in required_fields if not project_data.get(field)]
        
        if missing_fields:
            QMessageBox.warning(self, "Warning", 
                              f"请先配置以下关键字段: {', '.join(missing_fields)}")
            return

        self.publisher = PublisherWindow(copy.deepcopy(self.projects[self.current_project]))
        self.publisher.show()
        
    def open_rule_dialog(self):
        rule_dialog = RuleDialog(self.copy_rules)
        rule_dialog.rules_updated.connect(self.update_rule_list)
        if rule_dialog.exec():
            self.copy_rules = rule_dialog.get_rules()
            self.projects[self.current_project]["COPY_RULES"] = self.copy_rules
            self.save_projects()
            self.update_rule_list()

    def update_rule_list(self):
        self.rule_list.clear()
        for rule_name, rule_data in self.copy_rules.items():
            self.rule_list.addItem(rule_name)
            for sub_rule in rule_data["sub_rules"]:
                self.rule_list.addItem(f"  {sub_rule['sub_rule']}")
                
    def export_projects(self):
        """导出所有项目配置到JSON文件"""
        file_path, _ = QFileDialog.getSaveFileName(self, "Export Projects", "", "JSON Files (*.json)")
        if file_path:
            try:
                with open(file_path, 'w') as f:
                    json.dump(self.projects, f, indent=4)
                QMessageBox.information(self, "Success", "Projects exported successfully!")
            except Exception as e:
                QMessageBox.warning(self, "Error", f"Failed to export projects: {str(e)}")
    
    def import_projects(self):
        """从JSON文件导入项目配置"""
        file_path, _ = QFileDialog.getOpenFileName(self, "Import Projects", "", "JSON Files (*.json)")
        if file_path:
            try:
                with open(file_path, 'r') as f:
                    imported_projects = json.load(f)
                
                # 检查并过滤掉已存在的项目
                new_projects = {}
                for name, data in imported_projects.items():
                    if name not in self.projects:
                        new_projects[name] = data
                
                if not new_projects:
                    QMessageBox.information(self, "Info", "No new projects to import.")
                    return
                
                # 合并项目
                self.projects.update(new_projects)
                self.save_projects()
                self.update_project_list()
                QMessageBox.information(self, "Success", f"Successfully imported {len(new_projects)} projects!")
            except Exception as e:
                QMessageBox.warning(self, "Error", f"Failed to import projects: {str(e)}")

    def open_email_settings(self):
        """配置SMTP服务器信息"""
        dialog = EmailSettingsDialog(self)
        dialog.exec()

class RuleDialog(QDialog):
    rules_updated = Signal(dict)

    def __init__(self, initial_rules):
        super().__init__()
        self.setWindowTitle("Manage Rules")
        # 获取屏幕尺寸并计算居中位置
        screen = QApplication.primaryScreen().availableGeometry()
        width, height = 1000, 600
        x = (screen.width() - width) // 2
        y = (screen.height() - height) // 2
        self.setGeometry(x, y, width, height)
        self.rules = initial_rules
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)

        # Rule name input
        self.rule_name = QLineEdit(placeholderText="Rule Name")
        layout.addWidget(QLabel("Rule Name:"))
        layout.addWidget(self.rule_name)

        # Sub-rule input
        self.sub_rule = QLineEdit(placeholderText="file_name=dest_path=dest_name 或者 file_name=dest_name")
        layout.addWidget(QLabel("Sub-rule (file_name=dest_path=dest_name 或者 file_name=dest_name):"))
        layout.addWidget(self.sub_rule)

        # Rule list
        self.rule_list = QListWidget()
        self.rule_list.setWordWrap(True)
        layout.addWidget(self.rule_list)

        # Buttons
        btn_layout = QHBoxLayout()
        btn_add = QPushButton("Add Rule")
        btn_add.clicked.connect(self.add_rule)
        btn_layout.addWidget(btn_add)

        btn_remove = QPushButton("Remove Rule")
        btn_remove.clicked.connect(self.remove_rule)
        btn_layout.addWidget(btn_remove)

        layout.addLayout(btn_layout)

        self.update_rule_list()

    def add_rule(self):
        rule_name = self.rule_name.text()
        if not rule_name:
            QMessageBox.warning(self, "Warning", "Please enter a rule name!")
            return
        rule = self.sub_rule.text()
        if not rule:
            QMessageBox.warning(self, "Warning", "Please enter a sub-rule!")
            return
        rule_parts = rule.split('=')
        if len(rule_parts) != 3 and len(rule_parts)!= 2:
            QMessageBox.warning(self, "Warning", "Invalid rule format!")
            return
        if rule_name not in self.rules:
            self.rules[rule_name] = {"sub_rules": []}
        self.rules[rule_name]["sub_rules"].append({"main_rule": rule_name, "sub_rule": rule})
        # self.sub_rule.clear()
        self.update_rule_list()
        self.rules_updated.emit(self.rules)

    def remove_rule(self):
        selected_items = self.rule_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "Please select a rule to remove!")
            return
        rule_text = selected_items[0].text()
        print(f"[DEBUG] Removing rule: {rule_text}")
        if rule_text.startswith("  "):
            # 是子规则
            try:
                # 改进主规则名称提取方式，从子规则文本中提取
                sub_rule_text = rule_text[2:]
                main_rule_name = next((name for name, rule in self.rules.items() 
                                     if any(item["sub_rule"] == sub_rule_text for item in rule["sub_rules"])), None)
                
                print(f"[DEBUG] Sub-rule text: {sub_rule_text} Main rule name: {main_rule_name}")
                
                # 在删除前显示主规则信息
                if main_rule_name:
                    reply = QMessageBox.question(self, "Confirm", 
                                          f"Are you sure you want to delete this sub-rule from {main_rule_name}?",
                                          QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
                    if reply != QMessageBox.StandardButton.Yes:
                        return
                    
                    print(f"[DEBUG] Removing sub-rule: {sub_rule_text} from main rule: {main_rule_name}")
                    print(f"[DEBUG] Current sub-rules for {main_rule_name}: {self.rules[main_rule_name]['sub_rules']}")
                    
                    # 精确匹配并删除子规则
                    self.rules[main_rule_name]["sub_rules"] = [
                        item for item in self.rules[main_rule_name]["sub_rules"] 
                        if item["sub_rule"] != sub_rule_text
                    ]
                    print(f"[DEBUG] Sub-rule removed successfully: {sub_rule_text}")
                    
                    # 如果主规则没有子规则了，则删除主规则
                    if not self.rules[main_rule_name]["sub_rules"]:
                        del self.rules[main_rule_name]
                        print(f"[DEBUG] Main rule {main_rule_name} deleted as it has no sub-rules")
                else:
                    QMessageBox.warning(self, "Warning", "Main rule not found for this sub-rule!")
                    print(f"[DEBUG] Main rule not found for sub-rule: {sub_rule_text}")
                    
            except Exception as e:
                QMessageBox.warning(self, "Warning", f"Failed to remove sub-rule: {str(e)}")
                print(f"[ERROR] Failed to remove sub-rule: {str(e)}")
        else:
            # 是主规则
            print(f"[DEBUG] Removing main rule: {rule_text}")
            if rule_text in self.rules:
                del self.rules[rule_text]
                print(f"[DEBUG] Main rule removed successfully: {rule_text}")
            else:
                QMessageBox.warning(self, "Warning", "Rule not found!")
                print(f"[DEBUG] Rule not found: {rule_text}")
        self.update_rule_list()
        self.rules_updated.emit(self.rules)

    def update_rule_list(self):
        self.rule_list.clear()
        for rule_name, rule_data in self.rules.items():
            self.rule_list.addItem(rule_name)
            for sub_rule in rule_data["sub_rules"]:
                self.rule_list.addItem(f"  {sub_rule['sub_rule']}")

    def get_rules(self):
        return self.rules

def get_remote_branches(git_url):
    """获取远程分支列表"""
    # 执行git命令获取远程分支列表
    result = subprocess.run(["git", "ls-remote", "--heads", git_url], 
                            capture_output=True, text=True)
    
    returncode = result.returncode
    if returncode != 0:
        return [returncode, None]

    # 解析分支名称
    branches = [line.split("refs/heads/")[-1].strip() 
                for line in result.stdout.splitlines() 
                if "refs/heads/" in line]
                
    return [returncode, branches]

class PublishWorker(QThread):
    log_signal = Signal(str, bool)  # 增加一个布尔参数以支持覆盖上一行输出
    finished = Signal(bool, str)

    def __init__(self, config, files, msg):
        super().__init__()
        self.config = config
        self.files = files
        self.msg = msg


    def excute_command(self, command, cwd=None, need_output=True):
        """执行命令"""
        try:
            
            # 控制哪些命令需要实时输出
            if command[1] in ["clone", "push", "fetch", "pull", "checkout"]:
                command.append("--progress")

            self.log_signal.emit(f'执行命令...{" ".join(command)}\n', False)

            env = os.environ.copy()
            env["GIT_PROGRESS_DELAY"] = "0"

            # 启动子进程
            process = subprocess.Popen(
                command,
                cwd=cwd,
                env=env,
                text=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                encoding='utf-8',
                errors='ignore',  # 忽略解码错误
                bufsize=1         # 行缓冲模式
            )

            # 实时读取输出
            if need_output:
                stdout_lines, stderr_lines = [], []
                stdout_thread = threading.Thread(target=self.read_stream, args=(process.stdout, "[STDOUT]", stdout_lines))
                stderr_thread = threading.Thread(target=self.read_stream, args=(process.stderr, "[STDERR]", stderr_lines))

                # 启动线程
                stdout_thread.start()
                stderr_thread.start()

                # 等待子进程结束
                process.wait()
                
                # 等待线程结束
                stdout_thread.join()
                stderr_thread.join()

                # 子进程已结束，直接获取返回码
                origin_code = process.returncode
                return_code = origin_code
                # if return_code == None:
                #     return_code = 0
                self.log_signal.emit(f"[DEBUG] need_output Command executed successfully with return code,origin_code: {origin_code},return_code: {return_code}",False)
                return [return_code, "\n".join(stdout_lines), "\n".join(stderr_lines)]
            else:
                # 如果不需要实时输出，则直接使用 communicate()
                stdout, stderr = process.communicate()
                origin_code = process.returncode
                return_code = origin_code
                # if return_code == None:
                #     return_code = 0
                self.log_signal.emit(f"[DEBUG] Command executed successfully with return code,origin_code: {origin_code},return_code: {return_code}",False)
                return [return_code, stdout, stderr]

        except Exception as e:
            self.log_signal.emit(f"执行过程中发生异常: {str(e)}", False)
            return [-1, "", str(e)]

    def read_stream(self, stream, prefix, lines_list):
        """读取流内容并记录日志"""
        buffer = ""  # 用于处理未完成的行
        try:
            while True:
                chunk = stream.read(1)  # 按字符读取
                if not chunk:  # 流结束
                    break

                buffer += chunk

                if "\r" in buffer or "\n" in buffer:
                    lines = buffer.splitlines(keepends=True)
                    for line in lines[:-1]:  # 处理完整行
                        processed_line = line.strip("\r\n")
                        self.process_line(processed_line, prefix)
                        lines_list.append(processed_line)
                    buffer = lines[-1]  # 保留不完整的行

            # 处理剩余的缓冲内容
            if buffer:
                processed_line = buffer.strip("\r\n")
                time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                print(f"[DEBUG] {time} Processing line: {processed_line}  False")
                self.process_line(processed_line, prefix)
                lines_list.append(processed_line)
        except Exception as e:
            self.log_signal.emit(f"执行过程中发生异常: {str(e)}", False)

        finally:
            # 确保流总是被关闭
            stream.close()

    def process_line(self, line, prefix):
        
        """处理每一行日志"""
        if line.startswith("remote: Counting objects:") or line.startswith("Filtering content:") or line.startswith("Updating files:") or line.startswith("Resolving deltas:") or line.startswith("Receiving objects:") or line.startswith("Counting objects:") or line.startswith("Compressing objects:") or line.startswith("Writing objects:") or line.startswith("Checking objects:") or line.startswith("remote: Counting objects:"):

            #获取line开头14个字符
            line_prefix = line[:14]
            if hasattr(self, 'last_progress_line') and self.last_progress_line and self.last_progress_line.startswith(line_prefix) :
                self.log_signal.emit(f"{prefix} {line}", True)    # 覆盖上一行            
            else:
                self.log_signal.emit(f"{prefix} {line}", False) 
            
            # 动态更新进度信息
            self.last_progress_line = line

        else:
            self.log_signal.emit(f"{prefix} {line}", False)
            # print(f"[DEBUG] Processing line: {line}  False")

    

    def get_current_branch(self):
        try:
            # 构造 Git 命令
            command = ["git", "rev-parse", "--abbrev-ref", "HEAD"]
            
            # 启动子进程执行 Git 命令
            process = subprocess.Popen(
                command,
                stdout=subprocess.PIPE,  # 捕获标准输出
                stderr=subprocess.PIPE,  # 捕获错误输出
                text=True                # 输出为字符串而非字节
            )
            
            # 等待子进程结束并获取输出
            stdout, stderr = process.communicate()
            
            # 判断命令是否成功
            if process.returncode == 0:
                branch_name = stdout.strip()  # 去除多余的换行符
                print(f"当前分支是: {branch_name}")
                return branch_name
            else:
                print("无法获取当前分支")
                print("错误信息:", stderr.strip())
                return -1
        
        except Exception as e:
            print("执行过程中发生异常:", str(e))
            return -1

    def clone_repo(self, repo_url, local_git_path):
        """克隆仓库"""
        self.log_signal.emit(f'CD到git操作空间 local_git_path:...{local_git_path}', False)
        
        # 克隆或更新仓库
        if not os.path.exists(local_git_path) or not os.path.exists(os.path.join(local_git_path, '.git')):
            # 如果目录不存在，则创建目录
            os.makedirs(local_git_path, exist_ok=True)
            self.log_signal.emit("开始克隆仓库...", False)
            command = ["git", "clone", repo_url, local_git_path]
            result = self.excute_command(command, local_git_path)
            return_code, stdout, stderr = result
            if return_code != 0:
                self.finished.emit(False, f"克隆仓库失败: {stderr.strip()} \n {stdout.strip()}")
                return
        os.chdir(local_git_path)
        

    def run(self):
        try:
            if mode == 1:
                local_git_path = str(self.config["LOCAL_GIT_PATH"])
                repo_url = self.config["REPO_URL"]
                self.clone_repo(repo_url, local_git_path)
                
                # 通过获取当前分支来判断是否是一个正常的git仓库，如果不是，则删除整个目录，然后重新克隆
                result = self.get_current_branch()
                if result == -1:
                    self.log_signal.emit(f"获取当前分支失败,删除本地仓库重新克隆", False)
                    shutil.rmtree(local_git_path)
                    self.clone_repo(repo_url, local_git_path)
                    result = self.get_current_branch()
                    if result == -1:
                        self.finished.emit(False, f"获取当前分支失败,删除本地仓库重新克隆失败")
                        return

                # 处理每个目标分支
                branches = self.config["TARGET_BRANCHES"].split(',')
                
                remote_branches_result = get_remote_branches(repo_url)
                return_code, remote_branches = remote_branches_result
                for branch in branches:
                    
                    branch = branch.strip()
                    self.log_signal.emit(f"正在处理分支：{branch}", False)
                    
                    #判断当前分支的远程分支是否存在，如果不存在，退出程序
                    if return_code != 0 or not remote_branches:
                        self.finished.emit(False, f"获取远程分支失败: {remote_branches}")
                        return

                    #打印远程分支列表
                    self.log_signal.emit(f"远程分支列表：{remote_branches}", False)

                    if branch not in remote_branches:
                        self.finished.emit(False, f"远程分支不存在: {branch}")
                        return

                    

                    current_branch = self.get_current_branch()
                    self.log_signal.emit(f"当前分支：{current_branch}", False)

                    # 检查分支是否存在
                    command = ["git", "rev-parse", "--verify", branch, "--", "HEAD"]
                    result = self.excute_command(command, local_git_path, False)
                    return_code, stdout, stderr = result
                    
                    if return_code == 0:

                        command = ["git", "fetch", "--prune"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        
                        command = ["git", "reset", "--hard", f"origin/{current_branch}"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        #如果远程分支不存在，就会出现这种情况，但是这种情况是可以正常处理的
                        if return_code!= 0:
                            self.log_signal.emit(f"重置本地分支失败: {stderr.strip()}", False)
                                                    
                        command = ["git", "clean", "-fd"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0:
                            self.finished.emit(False, f"清理工作区失败: {stderr.strip()}")
                            return
                        

                        command = ["git", "checkout", branch]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0:
                            self.finished.emit(False, f"切换分支失败: {stderr.strip()}")
                            return

                        command = ["git", "reset", "--hard", f"origin/{branch}"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0:
                            self.finished.emit(False, f"重置本地分支失败: {stderr.strip()}")
                            return

                        # 切换到该分支之后需要再次pull
                        command = ["git", "pull"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0 and stderr.strip().startswith("fatal:"):
                            self.finished.emit(False, f"拉取分支失败: {stderr.strip()}")
                            return
                    else:

                        self.log_signal.emit(f"拉取分支失败: {stderr.strip()} \n {stdout.strip()}", False)
                        self.log_signal.emit(f'分支不存在，需要创建分支...{branch}', False)

                        command = ["git", "fetch", "--prune"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result


                        command = ["git", "fetch", "--all"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code != 0:
                            self.finished.emit(False, f"更新远程分支失败: {stderr.strip()}")
                            return
                        
                        command = ["git", "reset", "--hard", f"origin/{current_branch}"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0:
                            self.log_signal.emit(f"重置本地分支失败: {stderr.strip()}", False)


                        command = ["git", "pull"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0 and stderr.strip().startswith("fatal:"):
                            self.finished.emit(False, f"拉取分支失败: {stderr.strip()} \n {stdout.strip()}")
                            return

                        command = ["git", "checkout", "-b", branch, f"origin/{branch}"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0:
                            self.finished.emit(False, f"创建分支失败: {stderr.strip()} \n {stdout.strip()}")
                            return
                        
                        command = ["git", "reset", "--hard", f"origin/{branch}"]
                        result = self.excute_command(command, local_git_path)
                        return_code, stdout, stderr = result

                        if return_code!= 0:
                            self.finished.emit(False, f"重置本地分支失败: {stderr.strip()}")
                            return
                    
                    # 复制文件
                    target_path =  self.config["TARGET_PATH"]
                    real_target_path = os.path.join(local_git_path, target_path)
                    print(f"[DEBUG] real_target_path: {real_target_path}")

                    if not os.path.exists(real_target_path):
                        os.makedirs(real_target_path)
                    
                    #解析COPY_RULES规则
                    for apk_file in self.files:
                        file_rules = []
                        for rule_group in self.config.get("COPY_RULES", {}).values():
                            file_rules.extend(rule_group.get("sub_rules", []))
                        print(f"匹配规则: {file_rules} \n")

                        file_name, dest_name, full_dest_path="","",""
                        for rule in file_rules:
                        
                            rule_parts = rule["sub_rule"].split('=')
                            if len(rule_parts)== 2:
                                # 处理file_name=dest_name的情况 这个一般国内项目用，只需要复制文件到指定目录修改文件名即可
                                file_name, dest_name = rule_parts
                                if file_name not in os.path.basename(apk_file):
                                    continue
                                full_dest_path = real_target_path
                            elif len(rule_parts)== 3:
                                # 处理file_name=dest_path=dest_name的情况 这个一般是国际项目用，需要复制文件到指定目录，修改文件名，并且指定目录
                                file_name, dest_path, dest_name = rule_parts
                                
                                if file_name not in os.path.basename(apk_file):
                                    continue

                                full_dest_path = os.path.join(local_git_path, target_path, dest_path)
                            else:
                                self.log_signal.emit(f"Invalid rule: {rule['sub_rule']}", False)
                                continue

                            
                            if not os.path.exists(full_dest_path):
                                os.makedirs(full_dest_path)
                            print(f"[DEBUG] 完整的目标路径: {full_dest_path}")
                            copy_file_with_optional_name(self, apk_file, full_dest_path, dest_name)


                        # 如果没有匹配到任何规则且不启用绝对匹配，则复制到目标路径
                        if not full_dest_path and not self.config.get("EXACT_MATCH", False):
                            full_dest_path = real_target_path
                            if not os.path.exists(full_dest_path):
                                os.makedirs(full_dest_path)
                            self.log_signal.emit(f"复制文件: {apk_file} 到 {full_dest_path},filename: {dest_name} \n", False)
                            copy_file_with_optional_name(self, apk_file, full_dest_path, None,)
                    self.log_signal.emit("文件复制完成  \n", False)

                    # 提交并推送
                    command = ["git", "add", "."]
                    result = self.excute_command(command, local_git_path)
                    return_code, stdout, stderr = result
                    if return_code != 0:
                        self.finished.emit(False, f"错误: {stderr.strip()} \n {stdout.strip()} ")
                        return

                    command = ["git", "commit", "-m", self.msg]
                    result = self.excute_command(command, local_git_path)
                    return_code, stdout, stderr = result
                    if return_code != 0:
                        self.finished.emit(False, f"错误: {stderr.strip()} \n {stdout.strip()} ")
                        return

                    command = ["git", "push", "-v", "origin", branch]
                    result = self.excute_command(command, local_git_path)
                    return_code, stdout, stderr = result
                    if return_code != 0:
                        self.finished.emit(False, f"错误: {stderr.strip()} \n {stdout.strip()}")
                        return
                    self.log_signal.emit("更改提交并推送成功 \n", False)
                
                self.finished.emit(True, self.msg)
            else:
                self.finished.emit(True, self.msg)
        except subprocess.CalledProcessError as e:
            self.finished.emit(False, f"Git operation failed: {str(e)} \n")
        except Exception as e:
            self.finished.emit(False, f"An error occurred: {str(e)} \n")

class MailSender:
    def __init__(self, smtp_server, smtp_port, smtp_user, smtp_password):
        self.smtp_server = smtp_server
        self.smtp_port = smtp_port
        self.smtp_user = smtp_user
        self.smtp_password = smtp_password

    def update_config(self, smtp_server, smtp_port, smtp_user, smtp_password):
        self.smtp_server = smtp_server
        self.smtp_port = smtp_port
        self.smtp_user = smtp_user
        self.smtp_password = smtp_password

    def send_email(self, receivers, subject, body, attachments=None):
        msg = MIMEMultipart()
        msg['From'] = str(Header(self.smtp_user, "utf-8"))  # 确保是字符串类型，而不是Heade
        to_address = ", ".join(receivers) if isinstance(receivers, list) else receivers
        print(f"To: {to_address}")  # 打印发送的邮箱地址，用于调试
        msg['To'] = str(Header(to_address, "utf-8"))
        msg['Subject'] = str(Header(subject, "utf-8"))

        msg.attach(MIMEText(body, 'html', 'utf-8'))

        if attachments:
            for attachment in attachments:
                if not os.path.exists(attachment):
                    raise FileNotFoundError(f"附件文件不存在: {attachment}")
                with open(attachment, 'rb') as f:
                    part = MIMEApplication(f.read(), Name=os.path.basename(attachment))
                part['Content-Disposition'] = f'attachment; filename="{os.path.basename(attachment)}"'
                msg.attach(part)

        try:
            print(f"SMTP服务器: {self.smtp_server}, 端口: {self.smtp_port}")
            with smtplib.SMTP_SSL(self.smtp_server, int(self.smtp_port), timeout=30) as server:
                server.login(self.smtp_user, self.smtp_password)
                server.sendmail(self.smtp_user, receivers, msg.as_string())
                server.quit()
        except (smtplib.SMTPException, TimeoutError) as e:
            print(f"邮件发送失败: {str(e)}")
        except FileNotFoundError as e:
            print(f"邮件发送失败: {str(e)}")
        except Exception as e:
            print(f"邮件发送失败: {str(e)}")

class UploadFile:
    def __init__(self, file_path="", file_name="", package_name="", version_name="", app_name=""):
        self.file_path = file_path
        self.file_name = file_name
        self.package_name = package_name
        self.version_name = version_name
        self.app_name = app_name

class PublisherWindow(QMainWindow):
    log_signal = Signal(str, bool)
       
    class ScreenWorker(QThread):
        finished_signal = Signal(int, str, list)  # 用于发送完成状态、错误信息和截屏图片路径
        log_signal = Signal(str, bool)
        def __init__(self, config, message):
            super().__init__()
            self.config = config
            self.screenshot = None
            self.message = message
            self.init_screenshot()
        
        def init_screenshot(self):
            # 从配置中获取用户名和密码
            username = self.config.get("SERVER_ACCOUNT", "")  # 替换为实际用户名
            password = self.config.get("SERVER_PWD", "")  # 替换为实际密码
            screenshot_url = self.config.get("SCREENSHOT_URL", "")
            parsed_url = urlparse(screenshot_url)
            login_url = f"{parsed_url.scheme}://{parsed_url.netloc}/users/sign_in"
            if "desaysv.com" in login_url:
                login_url = f"{login_url}#login-pane"
            print(f"[DEBUG] login_url: {login_url}" )

            self.screenshot = Screenshot(username, password, login_url)
            self.screenshot.log_signal.connect(self.on_screenshot_log)

        def on_screenshot_log(self, message):
            self.log_signal.emit(message, False)

        def run(self):
            try:
                # 执行屏幕截图操作
                # 如果启用了截屏功能，执行截屏
                if self.config.get("SCREENSHOT_ENABLED", False):
                    screenshot_url = self.config.get("SCREENSHOT_URL", "")
                    if screenshot_url and self.screenshot:
                        screen_pictures = []
                                                    # 遍历目标分支并截图
                        for branch in self.config["TARGET_BRANCHES"].split(','):
                            branch = branch.strip()
                            #默认美佳的分支路径格式
                            target_url = f"{screenshot_url}/tree/{branch}/{self.config['TARGET_PATH']}"
                            if "desaysv.com" in target_url:
                                target_url = f"{screenshot_url}/-/tree/{branch}"
                            # 创建截图保存路径
                            print(f"[DEBUG] target_url: {target_url}" )
                            screenshot_dir = os.path.join(os.path.dirname(self.config["LOCAL_GIT_PATH"]), "screenshots")
                            print(f"[DEBUG] screenshot_dir: {screenshot_dir}" )
                            if not os.path.exists(screenshot_dir):
                                os.makedirs(screenshot_dir)
                        
                            screenshot_path = os.path.join(screenshot_dir, f"{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.png")
                            if self.screenshot.screen(target_url, screenshot_path):
                                screen_pictures.append(screenshot_path)
                        # self.screenshot.close()

                        if not screen_pictures or len(screen_pictures) == 0:
                            self.finished_signal.emit(0, "截屏失败，没有获取到任何截图", None)
                        else:
                            self.finished_signal.emit(1, "截屏成功", screen_pictures)
                    else:
                        self.finished_signal.emit(0, "截屏失败，未配置截图URL或截图对象", None)
                else:
                    self.finished_signal.emit(1, "未启用截屏,已跳过", None)
            except Exception as e:
                self.finished_signal.emit(0, f"截屏失败: {str(e)}", None)

    
    class EmailWorker(QThread):
        finished_signal = Signal(int, str)

        def __init__(self, mail_sender, receivers, subject, body, attachments=None, screenshot_paths=None):
            super().__init__()
            self.mail_sender = mail_sender
            self.receivers = receivers
            self.subject = subject
            self.body = body
            self.attachments = attachments or []
            self.screenshot_paths = screenshot_paths or []

        def update_config(self, mail_sender, receivers, subject, body, attachments=None, screenshot_paths=None):
            self.mail_sender = mail_sender
            self.receivers = receivers
            self.subject = subject
            self.body = body
            self.attachments = attachments or []
            self.screenshot_paths = screenshot_paths or []

        def run(self):
            try:
                self.mail_sender.send_email(self.receivers, self.subject, self.body, self.attachments)
                self.finished_signal.emit(1, "邮件发送成功")
            except Exception as e:
                error_msg = f"邮件发送失败: {str(e)}"
                if "getaddrinfo" in str(e):
                    error_msg = f"SMTP服务器地址无效或网络连接失败 {str(e)}"
                self.finished_signal.emit(0, error_msg)

    def __init__(self, project_config):
        super().__init__()
        self.project_config = project_config
        self.mail_sender = None
        self.worker = None

        # 从REPO_URL提取项目名称
        repo_url = project_config.get("REPO_URL", "")
        project_name = project_config.get("PROJECT_NAME", "")
        
        self.setWindowTitle(f"{project_name} - {repo_url}")

        # 获取屏幕尺寸并计算居中位置
        screen = QApplication.primaryScreen().availableGeometry()
        width, height = 1200, 800
        x = (screen.width() - width) // 2
        y = (screen.height() - height) // 2
        self.setGeometry(x, y, width, height)
        
        self.init_ui()
        self.log_signal.connect(self.append_log)
        self.worker = None
        self.upload_files = []  # 存储的是对象
        self.apk_files = []    # 存储的是路径

        smtp_server, smtp_port, smtp_user, smtp_password = load_email_settings()

        if smtp_server and smtp_port and smtp_user and smtp_password:
            print(f"SMTP配置: {smtp_server}, {smtp_port}, {smtp_user}, {smtp_password}")
            if not self.mail_sender:
                self.mail_sender = MailSender(smtp_server, smtp_port, smtp_user, smtp_password)
            else:
                self.mail_sender.update_config(smtp_server, smtp_port, smtp_user, smtp_password)

        self.email_worker = None
        self.screen_worker = None
        self.file_locks = {}
        self.temp_files = []

    def init_ui(self):
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        layout = QVBoxLayout(main_widget)
        layout.setSpacing(15)
        layout.setContentsMargins(20, 20, 20, 20)

        # Card container for file selection
        file_card = QWidget()

        file_layout = QVBoxLayout(file_card)
        file_layout.setSpacing(10)

        # File selection
        lbl_files = QLabel("APK Files:")
        file_layout.addWidget(lbl_files)
        
        file_selection_layout = QHBoxLayout()
        file_selection_layout.setSpacing(10)
        
        self.file_list = QListWidget()
        self.file_list.setFixedHeight(150)
        file_selection_layout.addWidget(self.file_list, stretch=3)

        # 添加右键菜单
        self.file_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.file_list.customContextMenuRequested.connect(self.show_context_menu)



        
        self.btn_add = QPushButton("Add Files")
        self.btn_add.setFixedHeight(80)

        self.btn_add.clicked.connect(self.add_apk_files)
        file_selection_layout.addWidget(self.btn_add, stretch=1)
        
        file_layout.addLayout(file_selection_layout)
        layout.addWidget(file_card)

        # Card container for commit message
        commit_card = QWidget()

        commit_layout = QVBoxLayout(commit_card)
        commit_layout.setSpacing(10)
        
        lbl_msg = QLabel("Commit Message:")
        commit_layout.addWidget(lbl_msg)
        
        self.commit_msg = QTextEdit()
        self.commit_msg.setMinimumHeight(120)
        self.commit_msg.setWordWrapMode(QTextOption.WrapMode.WrapAnywhere)
        commit_layout.addWidget(self.commit_msg)
        
        layout.addWidget(commit_card)

        # Email configuration
        email_card = QWidget()
        email_layout = QVBoxLayout(email_card)
        email_layout.setSpacing(10)
        email_layout.addWidget(QLabel("Email to:"))
        self.email_input = QLineEdit(placeholderText="收件人邮箱(逗号,隔开)")
        email_layout.addWidget(self.email_input)
        layout.addWidget(email_card)
        # 隐藏Email配置 因为飞书暂无法实现
        # email_card.setVisible(False)
        # self.email_input.setVisible(False)

        # Log window card
        log_card = QWidget()

        log_layout = QVBoxLayout(log_card)
        
        lbl_log = QLabel("Operation Log:")
        log_layout.addWidget(lbl_log)
        
        self.log_window = QTextEdit()
        self.log_window.setReadOnly(True)
        self.log_window.setWordWrapMode(QTextOption.WrapMode.WrapAnywhere)

        self.log_window.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        self.log_window.setFixedHeight(200)
        log_layout.addWidget(self.log_window)
        
        layout.addWidget(log_card)

        # Publish button with shadow effect
        btn_container = QWidget()
        btn_layout = QHBoxLayout(btn_container)
        btn_layout.setContentsMargins(0, 0, 0, 0)
        
        self.btn_publish = QPushButton("Publish to GitLab")

        self.btn_publish.clicked.connect(self.publish)
        btn_layout.addWidget(self.btn_publish, alignment=Qt.AlignmentFlag.AlignCenter)
        
        layout.addWidget(btn_container)
        self.apk_progress_dialog = QProgressDialog("正在上传...", "", 0, 0, self)
        self.apk_progress_dialog.setWindowTitle("上传中")
        # self.apk_progress_dialog.setWindowFlags(self.apk_progress_dialog.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
        self.apk_progress_dialog.setModal(True)
        self.apk_progress_dialog.setCancelButton(None)
        self.apk_progress_dialog.reset()


        self.screenshot_progress_dialog = QProgressDialog("正在截屏...", "", 0, 0, self)
        self.screenshot_progress_dialog.setWindowTitle("截屏中")
        # self.screenshot_progress_dialog.setWindowFlags(self.screenshot_progress_dialog.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
        self.screenshot_progress_dialog.setModal(True)
        self.screenshot_progress_dialog.setCancelButton(None)
        self.screenshot_progress_dialog.reset()

        self.email_progress_dialog = QProgressDialog("正在发送邮件...", "", 0, 0, self)
        self.email_progress_dialog.setWindowTitle("发送邮件中")
        # self.email_progress_dialog.setWindowFlags(self.email_progress_dialog.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
        self.email_progress_dialog.setModal(True)
        self.email_progress_dialog.setCancelButton(None)
        self.email_progress_dialog.reset()

        self.file_progress_dialog = QProgressDialog("文件处理中，请稍后...", "", 0, 0, self)
        self.file_progress_dialog.setWindowTitle("文件处理")
        self.file_progress_dialog.setWindowFlags(self.file_progress_dialog.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint)
        self.file_progress_dialog.setModal(True)
        self.file_progress_dialog.setCancelButton(None)
        self.file_progress_dialog.reset()

    def show_context_menu(self, pos):
        # 获取右键点击位置的项目
        item = self.file_list.itemAt(pos)
        if not item:
            return
                    
        menu = QMenu()
        delete_action = menu.addAction("删除")
        delete_action.triggered.connect(lambda: self.delete_file(item.data(Qt.ItemDataRole.UserRole)))
        menu.exec(self.file_list.mapToGlobal(pos))

    def delete_file(self, uploadFile):
        
        if not uploadFile:
            return
            
        reply = QMessageBox.question(self, "Delete File", 
                                    f"Are you sure you want to delete {uploadFile.file_name}?",
                                    QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
        
        if reply == QMessageBox.StandardButton.Yes:
            self.upload_files.remove(uploadFile)
            self.update_file_list()

    def update_file_list(self):
        commit_msg = ""

        self.file_list.clear()
        for uploadFile in self.upload_files:
            itemText = f"{uploadFile.file_name}"
            if uploadFile.file_name.lower().endswith('.apk'):
                itemText = f"{uploadFile.file_name}({uploadFile.package_name} > {uploadFile.version_name})"

            item = QListWidgetItem(itemText)
            item.setData(Qt.ItemDataRole.UserRole, uploadFile)  # 使用UserRole存储对象
            self.file_list.addItem(item)
            # 对每个文件加锁
            self.file_locks[uploadFile.file_path] = threading.Lock()
            self.file_locks[uploadFile.file_path].acquire()

            if uploadFile.file_name.lower().endswith('.apk'):
                commit_msg = commit_msg + f"{uploadFile.app_name}({uploadFile.package_name} > {uploadFile.version_name})" + ","
            else:
                commit_msg = commit_msg + f"{uploadFile.file_name}" + ","

        commit_msg = commit_msg[:-1]
    
        
        # 根据COMMIT_FORMAT格式 来格式化commit_msg
        formatted_msg = COMMIT_FORMAT
        if self.commit_msg:
            formatted_msg = formatted_msg.replace(f'%1', "Lion")
            formatted_msg = formatted_msg.replace(f'%2', "3rdparty")
            formatted_msg = formatted_msg.replace(f'%3', f"{self.project_config.get('PROJECT_NAME', '')} {datetime.datetime.now().strftime('%Y%m%d')} 版本发布 \n  分支: {self.project_config.get('TARGET_BRANCHES', '')} 内容: {commit_msg} ")
            formatted_msg = formatted_msg.replace(f'%4', "No ID")
            formatted_msg = formatted_msg.replace(f'%5', "Testable/Untestable")
            self.commit_msg.setPlainText(formatted_msg)
        else:
            self.commit_msg.setPlainText("")

    class FileWorker(QThread):
        finished_signal = Signal(int, str)  # 用于发送完成状态和错误信息
        log_signal = Signal(str, bool)  # 用于发送日志信息
        files_handled_signal = Signal(list)  # 新增信号，用于传递选择的文件列表
        
        def __init__(self, select_files):
            super().__init__()
            self.select_files = select_files
            self.settings = QSettings("GitLabPublisher", "FileDialog")
            self.files_handled = []  # 存储的是对象

        def get_apk_info(self, file_path):
            try:
                apk = APK(file_path)
                package_name = apk.package
                version_name = apk.version_name
                app_name = apk.get_app_name()
                
                print(f"[DEBUG] package_name: {package_name}, version_name: {version_name}, app_name: {app_name}")
                return {"package_name": package_name, "version_name": version_name, "app_name": app_name}
            except Exception as e:
                print(f"Error retrieving APK info for {file_path}: {str(e)}")
                return None

        def run(self):
            try:
                if self.select_files:
                    for file_path in self.select_files:
                        try:
                            if file_path.lower().endswith('.apk'):
                                apk_info = self.get_apk_info(file_path)
                                if apk_info:
                                    self.files_handled.append(UploadFile(file_path, os.path.basename(file_path), 
                                                                           apk_info['package_name'], apk_info['version_name'], 
                                                                           apk_info['app_name']))
                            else:
                                self.files_handled.append(UploadFile(file_path, os.path.basename(file_path), "", "", ""))
                        except Exception as e:
                            self.log_signal.emit(f"获取APK信息失败: {file_path}, 错误: {str(e)}", False)

                    self.files_handled_signal.emit(self.files_handled)# 将处理后的文件列表传递给主界面
            except Exception as e:
                self.log_signal.emit(f"错误: {str(e)}", False)
                self.finished_signal.emit(0, str(e))

    def handle_files_selected(self, handled_files):
        """处理文件选择线程返回的文件列表"""
        
        if handled_files:
            for handled_file in handled_files:
                # 检查是否已经在upload_files中,循环遍历upload_files，检查是否存在相同的file_path
                for uploadFile in self.upload_files:
                    if uploadFile.file_path == handled_file.file_path:
                        # 如果已经存在，删除旧的对象
                        print(f"[DEBUG] 先删除已经存在相同的文件: {uploadFile.file_path}")
                        self.upload_files.remove(uploadFile)
                        break
            # 将新的文件对象添加到upload_files中
            self.upload_files.extend(handled_files)
            self.update_file_list()
        
        self.file_progress_dialog.close()

        
    def add_apk_files(self):

        settings = QSettings("GitLabPublisher", "FileDialog")
        last_dir = str(settings.value("last_dir", ""))
        files, _ = QFileDialog.getOpenFileNames(self, "Select APK Files", last_dir, "所有文件 (*.*);;文本文件 (*.txt);;图片文件 (*.png *.jpg)")
        if files:
            settings.setValue("last_dir", os.path.dirname(files[0]))
            # 创建并启动文件处理线程
            self.file_progress_dialog.show()
            self.file_worker = self.FileWorker(files)
            self.file_worker.files_handled_signal.connect(self.handle_files_selected)
            self.file_worker.log_signal.connect(self.append_log)
            self.file_worker.finished_signal.connect(self.on_publish_finished)
            self.file_worker.start()



    def publish(self):
        
        if not self.upload_files or len(self.upload_files) == 0:
            QMessageBox.warning(self, "Warning", "Please select at least one APK file!")
            return
        self.apk_files = [uploadFile.file_path for uploadFile in self.upload_files]
        self.log_window.clear()    
        self.btn_publish.setEnabled(False)
        self.btn_add.setEnabled(False)
        print(f"[DEBUG] 开始上传...progress_dialog.show()")
        self.apk_progress_dialog.show()
        if not self.worker:
            self.worker = PublishWorker(self.project_config, self.apk_files, self.commit_msg.toPlainText())
            self.worker.log_signal.connect(self.append_log)
            self.worker.finished.connect(self.on_publish_finished)
        else:
            self.worker.config = self.project_config
            self.worker.files = self.apk_files
            self.worker.msg = self.commit_msg.toPlainText()
        self.worker.start()
    
    def recover_button(self, status):
        self.btn_publish.setEnabled(status)
        self.btn_add.setEnabled(status)

    def on_publish_finished(self, success, message):
        self.apk_progress_dialog.close()
        self.file_progress_dialog.close()
        if success:
            self.log_window.append("Success 上传成功！")
            if self.project_config.get("SCREENSHOT_ENABLED", False):
                self.screen_notification(message)
            else:              
                self.send_email_notification(message)

        else:
            self.log_window.append(message)
            self.on_publish_error(message)
            self.recover_button(True)
    
    def on_publish_error(self, message):
        QMessageBox.critical(self, "Error", f"发布失败：{message}")

    def append_log(self, message, overwrite=False):

        """追加日志到 log_window"""
        # 检查输入的有效性
        if not message or not message.strip():
            return
        message = message.strip()

        # 获取当前光标
        cursor = self.log_window.textCursor()

        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        message = f"[{timestamp}] {message}"

        print(f"[DEBUG] 追加日志: {message}")

        if overwrite:
            # 检查文档是否为空
            if self.log_window.document().isEmpty():
                print("[DEBUG] 文档为空，直接追加日志")
                self.log_window.append(message)
                return

            # 移动光标到文档末尾
            cursor.movePosition(QTextCursor.MoveOperation.End)

            # 移动到最后一行的开头
            cursor.movePosition(QTextCursor.MoveOperation.StartOfLine, QTextCursor.MoveMode.MoveAnchor)

            # 确保仅选择最后一行的内容
            cursor.movePosition(QTextCursor.MoveOperation.EndOfLine, QTextCursor.MoveMode.KeepAnchor)

            # 调试信息：打印选中的文本
            selected_text = cursor.selectedText()


            # 删除选中的内容
            if selected_text.strip():  # 确保选中的文本不为空
                cursor.removeSelectedText()

            # 如果最后一行有换行符，删除换行符
            if cursor.position() > 0 and cursor.block().text().strip() == "":
                cursor.deletePreviousChar()

            # 设置新的光标位置
            self.log_window.setTextCursor(cursor)
            message = self._process_message(message)
            # 追加新的日志内容
            self.log_window.insertPlainText("\n" + message)  # 避免额外的换行符
        else:
            # 直接追加日志内容
            self.log_window.append(message)

        # 滚动到文档末尾
        self.log_window.verticalScrollBar().setValue(self.log_window.verticalScrollBar().maximum())

    def _process_message(self, message):
        """预处理 message：删除末尾的空行和换行符"""
        # 去除末尾的换行符和空白字符
        while message.endswith(("\n", "\r")):
            message = message[:-1].rstrip()

        # 返回处理后的消息
        return message


    def screen_notification(self, message):
        try:           
            if not self.screen_worker:
                self.screen_worker = self.ScreenWorker(self.project_config, message)
                self.screen_worker.finished_signal.connect(self.on_screen_finished)
                self.screen_worker.log_signal.connect(self.log_signal.emit)
            else:
                self.screen_worker.config = self.project_config
                self.screen_worker.message = message
            self.screen_worker.start()
            self.screenshot_progress_dialog.show()
        except Exception as e:
            error_msg = f"截屏失败: {str(e)}"
            self.log_signal.emit(error_msg, False)
            self.on_screen_finished(0, error_msg, None)


    def send_email_notification(self, message, screen_pictures = None):

        if not self.mail_sender or not self.mail_sender.smtp_server or not self.mail_sender.smtp_port or not self.mail_sender.smtp_user or not self.mail_sender.smtp_password:
            print(f"[DEBUG] 未配置邮件发送设置，跳过邮件发送")
            self.on_email_finished(0, "未配置邮件发送设置，跳过邮件发送")
            return

        if not self.email_input.text():
            print(f"[DEBUG] 未填写收件人，跳过邮件发送")
            self.on_email_finished(0, "未填写收件人，跳过邮件发送")
            return
        receivers_text = self.email_input.text()
        receivers = receivers_text if isinstance(receivers_text, list) else receivers_text.split(',')
        subject = "版本上传通知"
        body = f"""<html>
        <body>
        <h2>版本已成功上传到GitLab</h2>
        </br>
        <p>内容：{message}</p>
        """
        if screen_pictures:
            for img_path in screen_pictures:
                with open(img_path, "rb") as img_file:
                    encoded_img = base64.b64encode(img_file.read()).decode('utf-8')
                    body += f'<img src="data:image/png;base64,{encoded_img}"><br>'
        body += "</body></html>"
        
        try:
            if not self.email_worker:
                print(f"[DEBUG] 初始化邮件发送线程")
                self.email_worker = self.EmailWorker(self.mail_sender, receivers, subject, body)
                self.email_worker.finished_signal.connect(self.on_email_finished)
            else:
                print(f"[DEBUG] 复用邮件发送线程")
                self.email_worker.update_config(self.mail_sender, receivers, subject, body)

            
            self.email_worker.start()
            self.email_progress_dialog.show()
            
        except (smtplib.SMTPException, TimeoutError) as e:
            error_msg = f"邮件发送失败: {str(e)}"
            print(error_msg)
            self.on_email_finished(0, error_msg)
        except FileNotFoundError as e:
            error_msg = f"邮件发送失败: {str(e)}"
            print(error_msg)
            self.on_email_finished(0, error_msg)
        except Exception as e:
            error_msg = f"邮件发送失败: {str(e)}"
            print(error_msg)
            self.on_email_finished(0, error_msg)


    def on_email_finished(self, status, message):
        self.email_sending_flag = False
        self.log_signal.emit(message, False)
        self.email_progress_dialog.close()
        if status == 1:
            QMessageBox.information(self, "邮件通知", "邮件发送成功")
        else:
            QMessageBox.warning(self, "邮件通知", message)
        
        self.recover_button(True)

    def on_screen_finished(self, status, message, screen_pictures):
   
        self.screen_flag = False
        message=f"{self.commit_msg.toPlainText()} </br> {message}"

        self.log_signal.emit(message, False)

        self.screenshot_progress_dialog.close()

        #不管成功失败都发送邮件
        self.send_email_notification(message, screen_pictures)
            
    def terminate_threads(self):
        print(f"[DEBUG] 强制终止所有工作线程")
        """强制终止所有工作线程"""
        if hasattr(self, 'worker') and self.worker and self.worker.isRunning():
            self.worker.terminate()
            self.worker.wait()
        if hasattr(self, 'email_worker') and self.email_worker and self.email_worker.isRunning():
            self.email_worker.terminate()
            self.email_worker.wait()
        if hasattr(self, 'screen_worker') and self.screen_worker and self.screen_worker.isRunning():
            self.screen_worker.terminate()
            self.screen_worker.wait()
            
    def cleanup_resources(self):
        # 释放文件锁等资源
        if hasattr(self, 'file_locks'):
            for lock in self.file_locks.values():
                lock.release()
            self.file_locks.clear()
        
        # 清理临时文件
        if hasattr(self, 'temp_files'):
            for temp_file in self.temp_files:
                try:
                    os.remove(temp_file)
                except:
                    pass
            self.temp_files.clear()

    def closeEvent(self, event):
        # 清理工作线程
        self.terminate_threads()
        self.cleanup_resources()
        event.accept()

class EmailSettingsDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("邮件配置")
        # 获取屏幕尺寸并计算居中位置
        screen = QApplication.primaryScreen().availableGeometry()
        width, height = 400, 300
        x = (screen.width() - width) // 2
        y = (screen.height() - height) // 2
        self.setGeometry(x, y, width, height)
        self.init_ui()
        self.load_config()

    def init_ui(self):
        layout = QVBoxLayout(self)
        self.smtp_server = QLineEdit(placeholderText="SMTP服务器")
        layout.addWidget(QLabel("SMTP服务器:"))
        layout.addWidget(self.smtp_server)

        self.smtp_port = QLineEdit(placeholderText="SMTP端口")
        layout.addWidget(QLabel("SMTP端口:"))
        layout.addWidget(self.smtp_port)

        self.smtp_user = QLineEdit(placeholderText="SMTP用户名")
        layout.addWidget(QLabel("SMTP用户名:"))
        layout.addWidget(self.smtp_user)

        self.smtp_password = QLineEdit(placeholderText="SMTP密码")
        layout.addWidget(QLabel("SMTP密码:"))
        layout.addWidget(self.smtp_password)

        self.btn_save = QPushButton("保存")
        self.btn_save.clicked.connect(self.save_config)
        layout.addWidget(self.btn_save)

    def load_config(self):
        settings = QSettings("GitLabPublisher", "Projects")
        self.smtp_server.setText(str(settings.value("smtp_server", "")))
        self.smtp_port.setText(str(settings.value("smtp_port", 587)))
        self.smtp_user.setText(str(settings.value("smtp_user", "")))
        self.smtp_password.setText(str(settings.value("smtp_password", "")))

    def save_config(self):
        required_fields = [
            ("SMTP服务器", self.smtp_server),
            ("SMTP端口", self.smtp_port),
            ("SMTP用户名", self.smtp_user),
            ("SMTP密码", self.smtp_password)
        ]
        missing_fields = [field[0] for field in required_fields if not field[1].text().strip()]
        
        if missing_fields:
            QMessageBox.warning(self, "警告", f"以下SMTP配置不能为空: {', '.join(missing_fields)}")
            return
        settings = QSettings("GitLabPublisher", "Projects")
        settings.setValue("smtp_server", self.smtp_server.text())
        settings.setValue("smtp_port", self.smtp_port.text())
        settings.setValue("smtp_user", self.smtp_user.text())
        settings.setValue("smtp_password", self.smtp_password.text())
        QMessageBox.information(self, "成功", "配置保存成功！")
        self.accept()

def resource_path(relative_path):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setWindowIcon(QIcon(resource_path("icon.png")))
    window = ProjectManager()
    window.show()
    sys.exit(app.exec())
